﻿using System.Collections.Generic;
using System.IO;
using System.Text;

namespace GameFramework.Resource
{
    internal sealed partial class ResourceManager : GameFrameworkModule, IResourceManager
    {
        private sealed class PSReadWriteVersionList
        {
            private const int ResourceDataSize = 300;

            private List<Resource> m_Resources;
            private Dictionary<ResourceName, Resource> m_ResourceSearcher;
            private Queue<Resource> m_FreeResources;

            public PSReadWriteVersionList()
            {
                m_Resources = new List<Resource>();
                m_ResourceSearcher = new Dictionary<ResourceName, Resource>();
                m_FreeResources = new Queue<Resource>();
            }

            public List<Resource> Resources
            {
                get
                {
                    return m_Resources;
                }
            }

            public Resource AddOrUpdateResource(ResourceName resourceName, string fileSystemName, LoadType loadType, uint length, int hashCode)
            {
                Resource resource = null;
                if (m_ResourceSearcher.TryGetValue(resourceName, out resource))
                {
                    // Update
                    if (!resource.IsValid)
                    {
                        GameFrameworkLog.Error("Internal error.");
                    }

                    if (resource.FileSystemName != fileSystemName)
                    {
                        resource.FileSystemName = fileSystemName;
                        resource.IsDirty = true;
                    }

                    if (resource.LoadType != (byte)loadType)
                    {
                        resource.LoadType = (byte)loadType;
                        resource.IsDirty = true;
                    }

                    if (resource.Length != length)
                    {
                        resource.Length = length;
                        resource.IsDirty = true;
                    }

                    if (resource.HashCode != hashCode)
                    {
                        resource.HashCode = hashCode;
                        resource.IsDirty = true;
                    }
                }
                else
                {
                    // Add
                    resource = GetFreeResource();
                    resource.IsDirty = true;
                    resource.IsValid = true;
                    resource.Name = resourceName.Name;
                    resource.Variant = resourceName.Variant;
                    resource.Extension = resourceName.Extension;
                    resource.FileSystemName = fileSystemName;
                    resource.LoadType = (byte)loadType;
                    resource.Length = length;
                    resource.HashCode = hashCode;
                    m_ResourceSearcher.Add(resourceName, resource);
                }

                return resource;
            }

            public void RemoveResource(ResourceName resourceName)
            {
                Resource resource = null;
                if (m_ResourceSearcher.TryGetValue(resourceName, out resource))
                {
                    m_ResourceSearcher.Remove(resourceName);
                    m_FreeResources.Enqueue(resource);
                    resource.IsDirty = true;
                    resource.IsValid = false;
                    resource.Name = null;
                    resource.Variant = null;
                    resource.Extension = null;
                    resource.FileSystemName = null;
                    resource.LoadType = 0;
                    resource.Length = 0;
                    resource.HashCode = 0;
                }
            }

            public void Clear()
            {
                m_Resources.Clear();
                m_ResourceSearcher.Clear();
                m_FreeResources.Clear();
            }

            public void Load(Stream stream)
            {
                using (BinaryReader binaryReader = new BinaryReader(stream, Encoding.UTF8))
                {
                    int count = binaryReader.ReadInt32();
                    m_Resources = new List<Resource>(count);
                    m_ResourceSearcher = new Dictionary<ResourceName, Resource>(count);
                    for (int i = 0; i < count; i++)
                    {
                        int position = GetResourceOffset(i);
                        binaryReader.BaseStream.Position = position;
                        bool isValid = binaryReader.ReadBoolean();
                        if (isValid)
                        {
                            string str = null;
                            Resource resource = new Resource();
                            resource.IsDirty = false;
                            resource.IsValid = true;
                            str = binaryReader.ReadString();
                            resource.Name = string.IsNullOrEmpty(str) ? null : string.Intern(str);
                            str = binaryReader.ReadString();
                            resource.Variant = string.IsNullOrEmpty(str) ? null : string.Intern(str);
                            str = binaryReader.ReadString();
                            resource.Extension = string.IsNullOrEmpty(str) ? null : string.Intern(str);
                            str = binaryReader.ReadString();
                            resource.FileSystemName = string.IsNullOrEmpty(str) ? null : string.Intern(str);
                            resource.LoadType = binaryReader.ReadByte();
                            resource.Length = binaryReader.ReadUInt32();
                            resource.HashCode = binaryReader.ReadInt32();
                            m_Resources.Add(resource);
                            m_ResourceSearcher.Add(new ResourceName(resource.Name, resource.Variant, resource.Extension), resource);

                            if (stream.Position - position > ResourceDataSize)
                            {
                                GameFrameworkLog.Error("PSReadWriteVerionList.Save out of range.");
                            }
                        }
                        else
                        {
                            Resource resource = new Resource();
                            m_Resources.Add(resource);
                            m_FreeResources.Enqueue(resource);
                        }
                    }

                    if (m_Resources.Count != count)
                    {
                        GameFrameworkLog.Error("Internal error.");
                    }
                }
            }

            public void Save(Stream stream)
            {

                using (BinaryWriter binaryWriter = new BinaryWriter(stream, Encoding.UTF8))
                {
                    int count = m_Resources.Count;
                    binaryWriter.Write(count);
                    for (int i = 0; i < count; i++)
                    {
                        Resource resource = m_Resources[i];
                        if (!resource.IsDirty)
                        {
                            continue;
                        }

                        resource.IsDirty = false;
                        int position = GetResourceOffset(i);
                        binaryWriter.BaseStream.Position = position;
                        binaryWriter.Write(resource.IsValid);
                        binaryWriter.Write(resource.Name ?? string.Empty);
                        binaryWriter.Write(resource.Variant ?? string.Empty);
                        binaryWriter.Write(resource.Extension ?? string.Empty);
                        binaryWriter.Write(resource.FileSystemName ?? string.Empty);
                        binaryWriter.Write(resource.LoadType);
                        binaryWriter.Write(resource.Length);
                        binaryWriter.Write(resource.HashCode);
                        if (binaryWriter.BaseStream.Position - position > ResourceDataSize)
                        {
                            GameFrameworkLog.Error("PSReadWriteVerionList.Save out of range.");
                        }
                    }
                }

            }

            private int GetResourceOffset(int index)
            {
                return sizeof(int) /* count */ + ResourceDataSize * index;
            }

            private Resource GetFreeResource()
            {
                if (m_FreeResources.Count > 0)
                {
                    return m_FreeResources.Dequeue();
                }

                Resource resource = new Resource();
                m_Resources.Add(resource);
                return resource;
            }

            public sealed class Resource
            {
                public bool IsDirty
                {
                    get;
                    set;
                }

                public bool IsValid
                {
                    get;
                    set;
                }

                public string Name
                {
                    get;
                    set;
                }

                public string Variant
                {
                    get;
                    set;
                }

                public string Extension
                {
                    get;
                    set;
                }

                public string FileSystemName
                {
                    get;
                    set;
                }

                public byte LoadType
                {
                    get;
                    set;
                }

                public uint Length
                {
                    get;
                    set;
                }

                public int HashCode
                {
                    get;
                    set;
                }
            }
        }
    }
}
